//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Text;

namespace OpenLS.Spreadsheet
{
    internal class DoubleCellBag
    {
        public DoubleCellBag(Worksheet worksheet) 
        {
            _nonVolatileCellBag = new NonVolatileCellBag(worksheet);
            _volatileCells = new Dictionary<CellAddress, WeakReference>(new CellAddressComparer());
            _volatileCellLock = new object();
            _worksheet = worksheet;
        }

        private readonly Worksheet _worksheet;

        public NonVolatileCellBag   NonVolatileCells
        {
            get { return _nonVolatileCellBag; }
        }
        public Cell GetVolatileCellIfAny (CellAddress address)
        {
            WeakReference result;
            if (!VolatileCells.TryGetValue(address, out result))
                return null;
            Cell cell = (Cell) result.Target;
            return cell;
        }

        private readonly NonVolatileCellBag _nonVolatileCellBag;
        private readonly Dictionary<CellAddress, WeakReference> _volatileCells;

        private Dictionary<CellAddress, WeakReference> VolatileCells
        {
            get
            {
                return _volatileCells;
            }
        }
        internal int VolatileCellCount { get { return _volatileCells.Count; } }
        internal IEnumerable<Cell> GetVolatileCells()
        {
            {
                foreach (var item in VolatileCells.Values)
                    if (item.Target != null)
                        yield return (Cell) item.Target;
            }
        }

        private readonly object _volatileCellLock;

        public object VolatileCellLock
        {
            get { return _volatileCellLock; }
        }

#if DEBUG
        public bool IsLocked { get; set; }
#endif

        public void AddNonVolatileCell(Cell cell)
        {
#if DEBUG
            Contract.Requires(IsValid);
            Contract.Requires(!cell.IsVolatile);
            Contract.Requires(!ContainsVolatileCell(cell));
            Contract.Ensures(IsValid);
#endif
            NonVolatileCells.Add(cell);
        }

        public void RemoveVolatileCells(Predicate<CellAddress> predicate)
        {
            lock (_volatileCellLock)
            {
                var list = new List<Cell>(GetVolatileCells(predicate));
                foreach (Cell cell in list)
                    RemoveVolatileCell(cell);
            }
        }

        private IEnumerable<Cell> GetVolatileCells(Predicate<CellAddress> predicate)
        {
            foreach (CellAddress address in VolatileCells.Keys)
            {
                if (predicate(address))
                {
                    var cell = VolatileCells[address].Target as Cell;
                    if (cell != null)
                        yield return cell;
                }
            }
        }

        public void RemoveNonVolatileCellNoEvents(Cell cell)
        {
            Contract.Requires(!cell.IsVolatile);
            NonVolatileCells.BeginEdit();
            NonVolatileCells.Remove(cell);
            NonVolatileCells.EndEdit();
        }
        internal void BeginEdit()
        {
            NonVolatileCells.BeginEdit();
        }

        internal void EndEdit()
        {
            NonVolatileCells.EndEdit();
        }

        public void RemoveNonVolatileCellWithEvents(Cell cell)
        {
#if DEBUG
            Contract.Requires(IsValid);
            Contract.Requires(cell != null);
            Contract.Requires(!cell.IsVolatile);
            Contract.Requires(!ContainsVolatileCell(cell));
            Contract.Ensures(IsValid);
            Contract.Ensures(!Contains(cell));
#endif
            NonVolatileCells.Remove(cell);
            _worksheet.OnCellRemoved(cell);
            
        }

      

        internal void RemoveDeadVolatileCell(Cell c)
        {
            WeakReference o;
            lock (VolatileCellLock)
            {
                if (VolatileCells.TryGetValue(c.Address, out o))
                {
                    if (o.IsAlive)
                        return;
                    VolatileCells.Remove(c.Address);
                }
            }
        }

        internal void RemoveVolatileCell(Cell c)
        {
            lock (VolatileCellLock)
            {
                VolatileCells.Remove(c.Address);
            }
        }

        public void ForceVolatileCell(Cell cell)
        {
            lock (_volatileCellLock)
            {
                if (VolatileCells.ContainsKey(cell.Address))
                    VolatileCells.Remove(cell.Address);
                addVolatileCell(cell);
            }
        }

        public Cell this[CellAddress address]
        {
            get
            {
                Cell result = NonVolatileCells.GetItemIfAny(address);
                if (result != null)
                    return result;
                WeakReference wr;
                lock (VolatileCellLock)
                {
                    if (VolatileCells.TryGetValue(address, out wr))
                        if (wr.IsAlive)
                            return (Cell) wr.Target;
                        else
                            VolatileCells.Remove(address);
                    //\\ Debug.WriteLine("creating volatile cell " + address.Label);
                    result = Cell.CreateVolatileCell(_worksheet.Rows[address.Row], _worksheet.Columns[address.Column]);
                    addVolatileCell(result);
                }
                return result;
            }
        }

#if DEBUG

       
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
/*            foreach (CellAddress address in VolatileCells.Keys)
            {
                WeakReference wr = VolatileCells[address];
                if (wr.IsAlive)
                {
                    var cell = wr.Target as Cell;
                    if (cell != null)
                    {
                        if (! cell.Address.Equals(address))
                        {
                            Debug.WriteLine("problem with " + cell.Address.Label + " value =" + cell.Value + "  key = " +
                                            address.Label);
                        }
                        Contract.Invariant(cell.Address.Equals(address));
                    }
                }
            }
 * */
        }
#endif


        internal void AddCell(Cell cell)
        {
            if (cell.IsVolatile)
                addVolatileCell(cell);
            else
            {
                AddNonVolatileCell(cell);
            }
        }

        private void addVolatileCell(Cell cell)
        {
            Contract.Requires(cell.IsVolatile);
#if DEBUG
            Contract.Requires(!IsLocked);
#endif
            Contract.Requires(cell.IsVolatile);
            VolatileCells.Add(cell.Address, new WeakReference(cell));
        }


        internal void RemoveCellNoEvents(Cell cell)
        {
            if (cell.IsVolatile)
                RemoveVolatileCell(cell);
            else
                RemoveNonVolatileCellNoEvents(cell);
        }
        internal void RemoveCellWithEvents(Cell cell)
        {
#if DEBUG
            Contract.Requires(cell != null);
            Contract.Requires(IsValid);
            Contract.Requires(Contains(cell));
            Contract.Ensures(IsValid);
            Contract.Ensures(!Contains(cell));
#endif
            if (cell.IsVolatile)
                RemoveVolatileCell(cell);
            else
                RemoveNonVolatileCellWithEvents(cell);
        }

#if DEBUG

        public string DebugString
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                foreach (Cell cell in _nonVolatileCellBag)
                    sb.Append(cell.Address.Label + " -> " + cell.Value + Environment.NewLine);
                return sb.ToString();
            }
        }

        public bool Contains(Cell cell)
        {
            Contract.Requires(IsValid);
            bool result =  _nonVolatileCellBag.Contains(cell);
            if (result)
                return true;
            if (cell.IsVolatile)
            return ContainsVolatileCell(cell);
            return false;
        }

        internal bool ContainsVolatileCell(Cell cell)
        {
            bool b = _volatileCells.ContainsKey(cell.Address);
            if (b)
            {
                var v = _volatileCells[cell.Address];
                if (v.Target != null)
                {
                    return true;
                }
            }
            return false;
        }

        internal bool IsValid
        {
            get
            {
                return true;//\\
                foreach (Cell cell in this._nonVolatileCellBag)
                {
                    bool b = _volatileCells.ContainsKey(cell.Address);
                   //\\ Contract.Assert(!b);
                    if (b)
                    {
                        var wr = _volatileCells[cell.Address];
                        if (wr.Target != null)
                            return false;
                    }
                }

                return true;
            }
        }
#endif

        internal void UpdateCellAddress (Cell cell, IEnumerable<CellAddress> oldAddresses, IEnumerable<CellAddress> newAddresses)
        {
            Contract.Requires(oldAddresses != null);
            Contract.Requires(newAddresses != null);
            if (cell.IsVolatile)
                return;
            NonVolatileCells.UpdateCellAddress(cell, oldAddresses, newAddresses);
        }

        public Cell EnsurePromotedCellObsolete(CellAddress address)
        {
            {
                Contract.Ensures(Contract.Result<Cell>() != null);
                Contract.Ensures(!Contract.Result<Cell>().IsVolatile);
                Contract.Ensures(Contract.Result<Cell>().Row != null);
                Contract.Ensures(Contract.Result<Cell>().Column != null);
                Contract.Ensures(Contract.Result<Cell>().Row.Contains(Contract.Result<Cell>()) );
                Contract.Ensures(Contract.Result<Cell>().Column.Contains(Contract.Result<Cell>()));
                Cell result = NonVolatileCells.GetItemIfAny(address);
                if (result != null)
                    return result;
                result = GetVolatileCellIfAny(address);
                if (result != null)
                {
                    result.setRawValue(null);
                    _worksheet.Promote(result);
                    return result;
                }
                result = new Cell(this._worksheet.Rows[address.Row], _worksheet.Columns[address.Column]);
                throw new NotImplementedException();
              //  this._worksheet.AddCell(result);
                return result;
            }
        }
        
    }
}